#include <appmenu_updater.h>
#include <config.h>

#include <gtkmm.h>
using Glib::RefPtr;
using Glib::ustring;
using Gtk::manage;
#include <glibmm/i18n.h>

using sigc::mem_fun;
using sigc::ptr_fun;

#include <iomanip>
using std::setw;
using std::fixed;
using std::setprecision;

#include <iostream>
using std::cerr;
using std::endl;

#include <algorithm>
using std::max;
using std::min;

#include <libappindicator/app-indicator.h>

#include <process_struct.h>
#include <configuration.h>
#include <preferences_dialog.h>

/* FIXME
 * if the menu is empty when exiting, glib will report a strange error:
 * GLib-GObject-CRITICAL **: g_object_unref: assertion `G_IS_OBJECT (object)' failed */

#define ICON_NORMAL "weather-clear-night"
#define ICON_COOL   "weather-overcast"
#define ICON_WARM   "weather-showers-scattered"
#define ICON_HOT    "weather-showers"

enum {
    APP_NORMAL,
    APP_COOL,
    APP_WARM,
    APP_HOT
};

const char* APP_ICONS[] = {ICON_NORMAL, ICON_COOL, ICON_WARM, ICON_HOT};

appmenu_updater::appmenu_updater (configuration &nconfig):
state_ (APP_NORMAL),
indicator_ (app_indicator_new(PACKAGE_NAME,
                              APP_ICONS[APP_NORMAL],
                              APP_INDICATOR_CATEGORY_SYSTEM_SERVICES)),
menu_ (new Gtk::Menu()),
dialog_ (new preferences_dialog (nconfig)),
config_ (nconfig)
{
    /* setup menu */
    Gtk::MenuItem *menuitem;

    menuitem = manage (new Gtk::SeparatorMenuItem ());
    menu_->append (*menuitem);

    menuitem = manage (new Gtk::MenuItem (_("Preferences...")));
    menuitem->signal_activate().connect (
            mem_fun (*this, &appmenu_updater::activate_pref));
    menu_->append (*menuitem);

    menuitem = manage (new Gtk::MenuItem (_("Quit")));
    menuitem->signal_activate().connect (
            mem_fun (*this, &appmenu_updater::activate_quit));
    menu_->append (*menuitem);

    menu_->show_all ();

    add_no_process ();

    /* setup appindicator */
    app_indicator_set_icon (indicator_, APP_ICONS[APP_NORMAL]);
    app_indicator_set_status (indicator_, APP_INDICATOR_STATUS_ACTIVE);
    app_indicator_set_menu (indicator_, menu_->gobj ());
}

void appmenu_updater::process_added (const process_t &process){
    /* new menu item */
    add_menu_item (process.pid, get_process_markup (process));

    /* remove empty placer */
    if (empty_) {
        empty_ = false;
        process_removed (-1);
    }

    /* we must notify the appindicator to update menu,
     * this is a bug of appindicator, should be fixed in future */ 
    app_indicator_set_menu (indicator_, menu_->gobj ());
}

void appmenu_updater::process_removed (pid_t pid){
    /* VERY IMPORTANT NOTE
     * when removing a MenuItem, you must call container(namely Menu)'s 
     * remove method. DON'T use "delete pMenuItem", it will cause problems
     * that are very hard to debug, and will suck a good sunday afternoon 
     * of you. */

    /* also, good c++ codes should not contain any *delete* */

    if (process_menu_lists_.empty ())
        cerr << "trying to remove a pid from empty list:" << pid << endl;

    process_menu_lists_t::iterator it;
    it = process_menu_lists_.find (pid);

    if (it != process_menu_lists_.end ())
    {
        menu_->remove (*(it->second)); /* delete menu item */
        app_indicator_set_menu (indicator_, menu_->gobj ());
        process_menu_lists_.erase (it); /* refresh my list */
    } else {
        cerr << "remove invalid pid: " << pid << endl;
    }

    if (process_menu_lists_.empty ())
        add_no_process ();
}

void appmenu_updater::process_updated (const process_t &process){
    process_menu_lists_t::iterator it;
    it = process_menu_lists_.find (process.pid);

    if (it != process_menu_lists_.end ()){
        it->second->set_label (get_process_markup (process));
    } else {
        cerr << "update invalid pid: " << process.name << endl;
    }
}

void appmenu_updater::sys_update (const process_t &sysinfo){
    /* compute overload according to whole system information */

    int load = 0; /* 0-3 */
    int load_cpu, load_io;

    load_cpu = (sysinfo.cpu > 10 ? sysinfo.cpu : 0);
    load_io  = sysinfo.read_bytes + sysinfo.write_bytes;
    load_io  = (load_io > 1 ? load_io * 5 : 0);
    load_io  = min (load_io, 100);
    load     = max (load_io, load_cpu);
    load    /= 30;

    if (load != state_) {
        app_indicator_set_icon (indicator_, APP_ICONS[load]);
        state_ = load;
    }
}

void appmenu_updater::activate_quit (){
    Gtk::Main::quit ();
}

void appmenu_updater::activate_pref (){
    dialog_->show ();
}

ustring appmenu_updater::get_process_markup (const process_t &process){
    /* appindicator does not support pango markup now,
     * when it does, this will return a colored result. */
    ustring ret (process.name);
    ret += " [";

    if (process.cpu > config_.cpu_valve)
        ret += ustring (" ")
            += ustring::format (fixed, setprecision(1),
                    process.cpu)
            += ustring ("%");

    if (process.memory > config_.memory_valve)
        ret += ustring (" ")
            += ustring::format (fixed,
                    process.memory)
            += ustring ("mb");

    if (process.read_bytes > config_.io_valve)
        ret += ustring (" ")
            += ustring::format (fixed, setprecision(1),
                    process.read_bytes)
            += ustring ("mb/s reading");

    if (process.write_bytes > config_.io_valve)
        ret += ustring (" ")
            += ustring::format (fixed, setprecision(1),
                    process.write_bytes)
            += ustring ("mb/s writing");

    ret += " ]";

    return ret;
}

Gtk::MenuItem*
appmenu_updater::add_menu_item (pid_t pid, const ustring &text)
{
    Gtk::MenuItem *menuitem_new_process;
    menuitem_new_process = manage (
            new Gtk::MenuItem (text));

    menuitem_new_process->show ();
    menu_->prepend (*menuitem_new_process);
    process_menu_lists_[pid] = menuitem_new_process;

    return menuitem_new_process;
}

/* show an item when no process availble */
void appmenu_updater::add_no_process () {
    empty_ = true;
    add_menu_item (-1, _("No bad process now"));
    app_indicator_set_menu (indicator_, menu_->gobj ());
}
